package com.liuqianfei.markme.common;

import com.liuqianfei.util.JSONUtil;
import com.liuqianfei.util.RegExpUtil;
import com.liuqianfei.util.StringUtil;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Utils
{
    ////////////////////////////////////////////////////////////////////////////////

    public static boolean isValidEntityCode(int c)
    {
        /*eslint no-bitwise:0*/
        // broken sequence
        if (c >= 0xD800 && c <= 0xDFFF) { return false; }
        // never used
        if (c >= 0xFDD0 && c <= 0xFDEF) { return false; }
        if ((c & 0xFFFF) == 0xFFFF || (c & 0xFFFF) == 0xFFFE) { return false; }
        // control codes
        if (c >= 0x00 && c <= 0x08) { return false; }
        if (c == 0x0B) { return false; }
        if (c >= 0x0E && c <= 0x1F) { return false; }
        if (c >= 0x7F && c <= 0x9F) { return false; }
        // out of range
        return c <= 0x10FFFF;
    }

    public static String fromCodePoint(int c)
    {
        /*eslint no-bitwise:0*/
        if (c > 0xffff)
        {
            c -= 0x10000;
            int surrogate1 = 0xd800 + (c >> 10), surrogate2 = 0xdc00 + (c & 0x3ff);

            return String.valueOf((char) surrogate1) + String.valueOf((char) surrogate2);
        }
        return String.valueOf((char) c);
    }


    static String UNESCAPE_MD_RE  = "\\\\([!\"#$%&'()*+,\\-.\\/:;<=>?@[\\\\\\]^_`{|}~])";
    static String ENTITY_RE       = "&([a-z#][a-z0-9]{1,31});";
    static String UNESCAPE_ALL_RE = UNESCAPE_MD_RE + "|" + ENTITY_RE;

    static String DIGITAL_ENTITY_TEST_RE = "^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))";//i

    public static String replaceEntityPattern(String match, String name)
    {
        int code;

        Map<String, Object> entities = new HashMap<>();
        try(InputStream inputStream = Utils.class.getResourceAsStream("/entities.json"))
        {
            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            String data = new String(bytes);

            entities = JSONUtil.jsonToObject(data);
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

        if (entities.containsKey(name))
            return StringUtil.fromUnicode(entities.get(name).toString());

        if (name.charAt(0) == 0x23/* # */ && RegExpUtil.match(DIGITAL_ENTITY_TEST_RE, name, true))
        {
            code = StringUtil.startsWithIgnoreCase(String.valueOf(name.charAt(1)), "x") ?
                    Integer.parseInt(StringUtil.slice(name, 2), 16) : Integer.parseInt(StringUtil.slice(name, 1), 10);

            if (isValidEntityCode(code))
                return fromCodePoint(code);
        }

        return match;
    }

    public static String unescapeMd(String str)
    {
        if (!str.contains("\\")) { return str; }
        return str.replace(UNESCAPE_MD_RE, "$1");
    }

    public static String unescapeAll(String str)
    {
        if (!str.contains("\\") && !str.contains("&"))
            return str;


        return str;
//        return str.replace(UNESCAPE_ALL_RE, function (match, escaped, entity)
//        {
//            if (escaped) { return escaped; }
//            return replaceEntityPattern(match, entity);
//        });
    }

    ////////////////////////////////////////////////////////////////////////////////
    
    public static String replaceUnsafeChar(String str)
    {
        str = str.replaceAll("&", "&amp;");
        str = str.replaceAll("<", "&lt;");
        str = str.replaceAll(">", "&gt;");
        str = str.replaceAll("\"", "&quot;");
        return str;
    }
    
    public static String escapeHtml(String str)
    {
        Pattern pattern = Pattern.compile("[&<>\"]");
        Matcher matcher = pattern.matcher(str);
        if (matcher.find())
        {
            return replaceUnsafeChar(str);
        }
        return str;
    }

    ////////////////////////////////////////////////////////////////////////////////

//    static String REGEXP_ESCAPE_RE = "[.?*+^$[\\]\\\\(){}|-]";
//
//    public static String escapeRE(String str)
//    {
//        return str.replaceAll(REGEXP_ESCAPE_RE, "\\$&");
//    }

    ////////////////////////////////////////////////////////////////////////////////

    public static boolean isSpace(int code)
    {
        switch (code)
        {
            case 0x09:
            case 0x20:
                return true;
        }
        return false;
    }

    // Zs (unicode class) || [\t\f\v\r\n]
    public static boolean isWhiteSpace(char code)
    {
        if (code >= 0x2000 && code <= 0x200A)
        {
            return true;
        }
        switch (code)
        {
            case 0x09: // \t
            case 0x0A: // \n
            case 0x0B: // \v
            case 0x0C: // \f
            case 0x0D: // \r
            case 0x20:
            case 0xA0:
            case 0x1680:
            case 0x202F:
            case 0x205F:
            case 0x3000:
                return true;
        }
        return false;
    }

    ////////////////////////////////////////////////////////////////////////////////

    /*eslint-disable max-len*/
    static String UNICODE_PUNCT_RE = "[!-#%-\\*,-/:;\\?@\\[-\\]_\\{\\}\\xA1\\xA7\\xAB\\xB6\\xB7\\xBB\\xBF\\u037E\\u0387\\u055A-\\u055F\\u0589\\u058A\\u05BE\\u05C0\\u05C3\\u05C6\\u05F3\\u05F4\\u0609\\u060A\\u060C\\u060D\\u061B\\u061E\\u061F\\u066A-\\u066D\\u06D4\\u0700-\\u070D\\u07F7-\\u07F9\\u0830-\\u083E\\u085E\\u0964\\u0965\\u0970\\u0AF0\\u0DF4\\u0E4F\\u0E5A\\u0E5B\\u0F04-\\u0F12\\u0F14\\u0F3A-\\u0F3D\\u0F85\\u0FD0-\\u0FD4\\u0FD9\\u0FDA\\u104A-\\u104F\\u10FB\\u1360-\\u1368\\u1400\\u166D\\u166E\\u169B\\u169C\\u16EB-\\u16ED\\u1735\\u1736\\u17D4-\\u17D6\\u17D8-\\u17DA\\u1800-\\u180A\\u1944\\u1945\\u1A1E\\u1A1F\\u1AA0-\\u1AA6\\u1AA8-\\u1AAD\\u1B5A-\\u1B60\\u1BFC-\\u1BFF\\u1C3B-\\u1C3F\\u1C7E\\u1C7F\\u1CC0-\\u1CC7\\u1CD3\\u2010-\\u2027\\u2030-\\u2043\\u2045-\\u2051\\u2053-\\u205E\\u207D\\u207E\\u208D\\u208E\\u2308-\\u230B\\u2329\\u232A\\u2768-\\u2775\\u27C5\\u27C6\\u27E6-\\u27EF\\u2983-\\u2998\\u29D8-\\u29DB\\u29FC\\u29FD\\u2CF9-\\u2CFC\\u2CFE\\u2CFF\\u2D70\\u2E00-\\u2E2E\\u2E30-\\u2E44\\u3001-\\u3003\\u3008-\\u3011\\u3014-\\u301F\\u3030\\u303D\\u30A0\\u30FB\\uA4FE\\uA4FF\\uA60D-\\uA60F\\uA673\\uA67E\\uA6F2-\\uA6F7\\uA874-\\uA877\\uA8CE\\uA8CF\\uA8F8-\\uA8FA\\uA8FC\\uA92E\\uA92F\\uA95F\\uA9C1-\\uA9CD\\uA9DE\\uA9DF\\uAA5C-\\uAA5F\\uAADE\\uAADF\\uAAF0\\uAAF1\\uABEB\\uFD3E\\uFD3F\\uFE10-\\uFE19\\uFE30-\\uFE52\\uFE54-\\uFE61\\uFE63\\uFE68\\uFE6A\\uFE6B\\uFF01-\\uFF03\\uFF05-\\uFF0A\\uFF0C-\\uFF0F\\uFF1A\\uFF1B\\uFF1F\\uFF20\\uFF3B-\\uFF3D\\uFF3F\\uFF5B\\uFF5D\\uFF5F-\\uFF65]|\\uD800[\\uDD00-\\uDD02\\uDF9F\\uDFD0]|\\uD801\\uDD6F|\\uD802[\\uDC57\\uDD1F\\uDD3F\\uDE50-\\uDE58\\uDE7F\\uDEF0-\\uDEF6\\uDF39-\\uDF3F\\uDF99-\\uDF9C]|\\uD804[\\uDC47-\\uDC4D\\uDCBB\\uDCBC\\uDCBE-\\uDCC1\\uDD40-\\uDD43\\uDD74\\uDD75\\uDDC5-\\uDDC9\\uDDCD\\uDDDB\\uDDDD-\\uDDDF\\uDE38-\\uDE3D\\uDEA9]|\\uD805[\\uDC4B-\\uDC4F\\uDC5B\\uDC5D\\uDCC6\\uDDC1-\\uDDD7\\uDE41-\\uDE43\\uDE60-\\uDE6C\\uDF3C-\\uDF3E]|\\uD807[\\uDC41-\\uDC45\\uDC70\\uDC71]|\\uD809[\\uDC70-\\uDC74]|\\uD81A[\\uDE6E\\uDE6F\\uDEF5\\uDF37-\\uDF3B\\uDF44]|\\uD82F\\uDC9F|\\uD836[\\uDE87-\\uDE8B]|\\uD83A[\\uDD5E\\uDD5F]";

    // Currently without astral characters support.
    public static boolean isPunctChar(String ch)
    {
        return RegExpUtil.match(UNICODE_PUNCT_RE, ch, false);
    }

    // Markdown ASCII punctuation characters.
    //
    // !, ", #, $, %, &, ', (, ), *, +, ,, -, ., /, :, ;, <, =, >, ?, @, [, \, ], ^, _, `, {, |, }, or ~
    // http://spec.commonmark.org/0.15/#ascii-punctuation-character
    //
    // Don't confuse with unicode punctuation !!! It lacks some chars in ascii range.
    //
    public static boolean isMdAsciiPunct(char ch)
    {
        switch (ch)
        {
            case 0x21/* ! */:
            case 0x22/* " */:
            case 0x23/* # */:
            case 0x24/* $ */:
            case 0x25/* % */:
            case 0x26/* & */:
            case 0x27/* ' */:
            case 0x28/* ( */:
            case 0x29/* ) */:
            case 0x2A/* * */:
            case 0x2B/* + */:
            case 0x2C/* , */:
            case 0x2D/* - */:
            case 0x2E/* . */:
            case 0x2F/* / */:
            case 0x3A/* : */:
            case 0x3B/* ; */:
            case 0x3C/* < */:
            case 0x3D/* = */:
            case 0x3E/* > */:
            case 0x3F/* ? */:
            case 0x40/* @ */:
            case 0x5B/* [ */:
            case 0x5C/* \ */:
            case 0x5D/* ] */:
            case 0x5E/* ^ */:
            case 0x5F/* _ */:
            case 0x60/* ` */:
            case 0x7B/* { */:
            case 0x7C/* | */:
            case 0x7D/* } */:
            case 0x7E/* ~ */:
                return true;
            default:
                return false;
        }
    }

    // Hepler to unify [reference labels].
    //
    public static String normalizeReference(String str)
    {
        return str.trim().replaceAll("\\s+", " ").toUpperCase();
    }
    
    public static void main(String[] args)
    {
        String str = "ilovey&ou";
        Pattern pattern = Pattern.compile("[&<>\"]");
        Matcher matcher = pattern.matcher(str);
        System.out.println(matcher.find());
    }
}
